package cn.itcast.nems.studentrecord.service.settlement;

import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.ic.common.exception.v2.bean.BizStatusEnum;
import cn.itcast.nems.studentrecord.constant.ReferenceTypeEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

/**
 * @author liyong
 * create: 2023-11-16 16:30:21
 **/
public class DefaultSettlementCalculator implements SettlementCalculator {
    final Logger logger = LoggerFactory.getLogger(this.getClass());

    final SettlementResult originSettlementResult;

    /**
     * 结算单据类型
     */
    ReferenceTypeEnum referenceType;

    /**
     * 是否需要计算课耗
     */
    private boolean needCalConsumption = true;

    /**
     * @param originSettlementResult 已有的结算结果
     * @param referenceType          结算凭单类型
     */
    public DefaultSettlementCalculator(SettlementResult originSettlementResult,
                                       ReferenceTypeEnum referenceType) {
        this.originSettlementResult = originSettlementResult;
        this.referenceType = referenceType;
    }

    @Override
    public SettlementResult calculate(SettlementData settlementData) {
        SettlementResult result = new SettlementResult();
        result.setSettlementData(settlementData);
        result.setSettlementReason(this.referenceType.name());
        if (this.originSettlementResult == null) {
            this.doCalculate(result, settlementData);
        } else {
            result.setServiceDays(originSettlementResult.getServiceDays());
            result.setSettlementDays(originSettlementResult.getSettlementDays());
            result.setSettlementAmount(originSettlementResult.getSettlementAmount());
        }
        final int amount = this.calTransferOutAmount(settlementData, result.getSettlementAmount());
        result.setTransferAmount(amount);
        return result;
    }

    void doCalculate(SettlementResult result, SettlementData settlementData) {
        this.initNeedCalConsumption(settlementData);
        result.setSettlementDays(this.calSettlementDays(settlementData));
        result.setServiceDays(this.calServiceDays(settlementData, result.getSettlementDays()));
        final int amount = this.calSettlementAmount(settlementData, result.getServiceDays(), result.getSettlementDays());
        result.setSettlementAmount(amount);
    }

    /**
     * 初始化是否需要计算课耗
     */
    private void initNeedCalConsumption(SettlementData settlementData) {
        final Boolean delayFlag = settlementData.getDelayFlag();
        if (delayFlag != null && delayFlag) {
            this.needCalConsumption = false;
            return;
        }
        final Boolean suspensionFlag = settlementData.getSuspensionFlag();
        if (suspensionFlag != null && suspensionFlag) {
            this.needCalConsumption = false;
            return;
        }
        final LocalDate startDate = settlementData.getStartDate();
        if (startDate == null) {
            BizExceptionProducer.throwBadRequest(BizStatusEnum.BAD_REQUEST.name(), "班级%d没有开班日期", settlementData.getClazzId());
        }
        if (WAIT_START_DATE.isBefore(startDate)) {
            this.needCalConsumption = false;
        }
    }

    /**
     * 计算总服务天数（课程天数）；如果实际服务天数是0，应服务天数也为0，否则为：应服务天数=预计结课时间-进班时间+1
     *
     * @param settlementData 结算数据
     * @param settlementDays 实际结算（服务）天数
     * @return 服务天数
     */
    int calServiceDays(SettlementData settlementData, int settlementDays) {
        int result = 0;
        if (this.needCalConsumption && settlementDays > 0) {
            // final LocalDate startDate = settlementData.getStartDate();
            final LocalDate startDate = settlementData.getInDate().toLocalDate();
            final LocalDate gDate = settlementData.getPredictionGraduationDate();
            if (gDate == null) {
                BizExceptionProducer.throwBadRequest(BizStatusEnum.BAD_REQUEST.name(), "班级%d没有预计结课日期", settlementData.getClazzId());
            }
            final long days = ChronoUnit.DAYS.between(startDate, gDate) + 1;
            result = (int) days;
        }
        return result;
    }

    /**
     * 计算结算天数(出班日期或预计结课日期-入班日期或开班日期+1)
     * 同一天进出不算课耗
     *
     * @return 结算天数
     */
    int calSettlementDays(SettlementData settlementData) {
        int result = 0;
        if (this.needCalConsumption) {
            LocalDate sDate = settlementData.getStartDate();
            final LocalDateTime inDateTime = settlementData.getInDate();
            if (inDateTime != null) {
                LocalDate inDate = inDateTime.toLocalDate();
                sDate = inDate.isAfter(sDate) ? inDate : sDate;
            }
            LocalDate eDate = settlementData.getPredictionGraduationDate();
            final LocalDateTime settlementDateTime = settlementData.getSettlementDate();
            if (settlementDateTime != null) {
                final LocalDate settlementDate = settlementDateTime.toLocalDate();
                eDate = settlementDate.isBefore(eDate) ? settlementDate : eDate;
            }
            final long days = ChronoUnit.DAYS.between(sDate, eDate) + 1;
            result = (int) (days > 1 ? days : 0);
        }
        return result;
    }

    /**
     * 缺省计算方式：
     * 结算金额 = 课时费 = 结算天数*(应付金额/课程天数)
     *
     * @param serviceDays    课程天数
     * @param settlementDays 结算天数
     * @return 结算金额
     */
    int calSettlementAmount(SettlementData settlementData, int serviceDays, int settlementDays) {
        if (serviceDays == 0 || settlementDays == 0) {
            return 0;
        }
        return this.doCalSettlementAmount(settlementData, serviceDays, settlementDays);
    }

    /**
     * 缺省计算方式：
     * 结算金额 = 课时费 = 结算天数*(应付金额/课程天数)
     *
     * @param serviceDays    课程天数
     * @param settlementDays 结算天数
     * @return 结算金额
     */
    int doCalSettlementAmount(SettlementData settlementData, int serviceDays, int settlementDays) {
        // 应付金额
        final BigDecimal amountDue = BigDecimal.valueOf(settlementData.getActualAmountDue());
        final BigDecimal amount = amountDue.multiply(BigDecimal.valueOf((double) settlementDays / serviceDays)).setScale(0, RoundingMode.HALF_UP);
        return amount.intValue();
    }

    /**
     * 计算结算后剩余的金额，这部分钱是可以转出或退费；
     *
     * @param settlementData   参数对象
     * @param settlementAmount 结算金额
     * @return 剩余金额 >=0 公式： 已付金额 - 退费金额 - 结算金额 + 转班转入金额
     */
    int calTransferOutAmount(SettlementData settlementData, int settlementAmount) {
        int amount = settlementData.getPaidAmount()
                - settlementAmount
                - settlementData.getRefundAmount();
        final Integer transferFromAmount = settlementData.getTransferFromAmount();
        if (transferFromAmount != null) {
            amount += settlementData.getTransferFromAmount();
        }
        return Math.max(amount, 0);
    }
}
